package hotel;

import hotel.board.ExtendedBoard;
import hotel.board.IExtendedBoard;
import hotel.board.circuit.CaseStates;
import hotel.board.circuit.ICase;
import hotel.board.circuit.ICircuit;
import hotel.board.circuit.predefined.ClassicHotelCircuit;
import hotel.exception.WaitingOperationException;
import hotel.hotel.IProperty;
import hotel.hotel.Properties;
import hotel.player.IPlayer;
import hotel.util.VerticalLabelUI;

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.AbstractMap.SimpleEntry;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class AppTest extends JFrame {
	
	private static final long serialVersionUID = 7495781780691536957L;
	
	private Color[] caseColors = new Color[] {
			new Color(213, 254, 132),
			new Color(171, 228, 255),
			new Color(255, 241, 149),
			new Color(254, 224, 22)
	};
	
	private IExtendedBoard model;
	private Map<IPlayer, JLabel> names;
	private Map<IPlayer, JLabel> balances;
	private Map<IPlayer, Color> colors;
	private Map<Entry<ICase, Integer>, JButton> sideButtons;
	private Map<Entry<ICase, Integer>, JLabel> sideLabels;
	private Map<ICase, JLabel> casesCenter;
	private Map<IProperty, JButton> buy;
	private Map<IProperty, JButton> addBuilding;
	private Map<IProperty, JButton> addFunArea;
	private Map<IProperty, JLabel> owners;
	private Map<IProperty, JLabel> built;
	private JButton endTurn;
	private JButton rollMoveDice;
	private JButton rollBuildDice;
	private JLabel moveDiceValue;
	private JLabel buildDiceValue;
	private JLabel action;

	public static void main(String[] args) throws WaitingOperationException {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				new AppTest().init();
			}
		});
	}
	
	private void init() {
		createModel();
		createView();
		placeComponents();
		createController();
	}
	
	private void createModel() {
		model = new ExtendedBoard(new String[] { "Nico", "Marion", "Romain" }, new ClassicHotelCircuit());
	}
	
	private void createView() {
		// Joueurs
		IPlayer[] players = model.getPlayers();
		balances = new HashMap<IPlayer, JLabel>();
		names = new HashMap<IPlayer, JLabel>();
		for (IPlayer p : players) {
			balances.put(p, new JLabel(separate(p.getBalance()) + " €"));
			names.put(p, new JLabel(p.getName()));
		}
		// Cases
		ICase[] cases = model.getCircuit().getCases();
		Entry<ICase, Integer> position;
		sideButtons = new HashMap<Entry<ICase, Integer>, JButton>();
		sideLabels = new HashMap<Entry<ICase, Integer>, JLabel>();
		casesCenter = new HashMap<ICase, JLabel>();
		int side;
		IProperty prop;
		JLabel lab;
		for (ICase c : cases) {
			lab = new JLabel(" ", JLabel.CENTER);
			lab.setOpaque(true);
			lab.setBackground(caseColors[c.getState().ordinal()]);
			casesCenter.put(c, lab);
			// Droite
			side = ICase.RIGHT_SIDE;
			position = new SimpleEntry<ICase, Integer>(c, side);
			sideButtons.put(position, new JButton(" "));
			prop = c.getProperty(side);
			lab = new JLabel(prop == null ? " " : "" + prop.getName().charAt(0), JLabel.CENTER);
			lab.setOpaque(true);
			sideLabels.put(position, lab);
			// Gauche
			side = ICase.LEFT_SIDE;
			position = new SimpleEntry<ICase, Integer>(c, side);
			sideButtons.put(position, new JButton(" "));
			prop = c.getProperty(side);
			lab = new JLabel(prop == null ? " " : "" + prop.getName().charAt(0), JLabel.CENTER);
			lab.setOpaque(true);
			sideLabels.put(position, lab);
		}
		// Propriétés
		IProperty[] properties = model.getHotelsArea().getProperties();
		buy = new HashMap<IProperty, JButton>();
		addBuilding = new HashMap<IProperty, JButton>();
		addFunArea = new HashMap<IProperty, JButton>();
		owners = new HashMap<IProperty, JLabel>();
		built = new HashMap<IProperty, JLabel>();
		for (IProperty p : properties) {
			buy.put(p, new JButton("$"));
			addBuilding.put(p, new JButton("+b"));
			addFunArea.put(p, new JButton("+a"));
			owners.put(p, new JLabel(" "));
			built.put(p, new JLabel(getBuiltFor(p)));
		}
		// Dés
		rollMoveDice = new JButton("Lancer");
		rollBuildDice = new JButton("Lancer");
		moveDiceValue = new JLabel(" ");
		buildDiceValue = new JLabel(" ");
		// Ligne d'info
		action = new JLabel(" ");
		// Fin du tour
		endTurn = new JButton("Finir le tour");
	}
	
	private void placeComponents() {
		JPanel p0 = new JPanel(new GridLayout(0, 2, 10, 10)); {
			JPanel p = new JPanel(null); {
				p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
				// Titre
				JPanel q = new JPanel(); {
					q.add(new JLabel("HOTEL"));
				}
				p.add(q);
				// Joueurs
				Color[] col = new Color[] { Color.GREEN, Color.RED, Color.BLUE, Color.YELLOW };
				colors = new HashMap<IPlayer, Color>();
				q = new JPanel(new GridLayout(0, 5)); {
					IPlayer[] pl = model.getPlayers();
					JLabel name;
					for (int i = 0; i < pl.length; ++i) {
						if (i % 2 != 0) {
							q.add(new JPanel());
						}
						colors.put(pl[i], col[i]);
						name = names.get(pl[i]);
						name.setForeground(colors.get(pl[i]));
						q.add(name);
						q.add(balances.get(pl[i]));
					}
				}
				p.add(q);
				// Dés
				q = new JPanel(); {
					q.add(new JLabel("Dés : "));
					q.add(rollMoveDice);
					q.add(new JLabel("["));
					q.add(moveDiceValue);
					q.add(new JLabel("]"));
					q.add(rollBuildDice);
					q.add(new JLabel("["));
					q.add(buildDiceValue);
					q.add(new JLabel("]"));
				}
				p.add(q);
				// Ligne d'action
				q = new JPanel(); {
					q.add(action);
				}
				p.add(q);
				// Fin de tour
				q = new JPanel(); {
					q.add(endTurn);
				}
				p.add(q);
				// Circuit
				q = new JPanel(new GridLayout(0, 11)); {
					JPanel r;
					ICase[] cases = model.getCircuit().getCases();
					Entry<ICase, Integer> e;
					ICase c;
					JLabel step;
					for (int i = 0; i < cases.length; ++i) {
						c = cases[i];
						if (i == model.getCircuit().getBankPosition()) {
							step = new JLabel("Banque", JLabel.CENTER);
							step.setUI(new VerticalLabelUI(true));
							q.add(step);
						}
						if (i == model.getCircuit().getCityHallPosition()) {
							step = new JLabel("Mairie", JLabel.CENTER);
							step.setUI(new VerticalLabelUI(true));
							q.add(step);
						}
						r = new JPanel(new GridLayout(5, 0)); {
							e = new SimpleEntry<ICase, Integer>(c, ICase.LEFT_SIDE);
							r.add(sideLabels.get(e));
							r.add(sideButtons.get(e));
							r.add(casesCenter.get(c));
							e.setValue(ICase.RIGHT_SIDE);
							r.add(sideButtons.get(e));
							r.add(sideLabels.get(e));
						}
						q.add(r);
					}
				}
				p.add(q);
				// Propriétés
				q = new JPanel(new GridLayout(0, 2)); {
					JPanel r;
					for (IProperty prop : model.getHotelsArea().getProperties()) {
						r = new JPanel(); {
							r.add(buy.get(prop));
							r.add(new JLabel("["));
							r.add(owners.get(prop));
							r.add(new JLabel("]"));
							r.add(new JLabel(prop.getName()));
						}
						q.add(r);
						r = new JPanel(); {
							r.add(built.get(prop));
							r.add(addBuilding.get(prop));
							r.add(addFunArea.get(prop));
						}
						q.add(r);
					}
				}
				p.add(q);
			}
			p0.add(p);
			p0.add(getInfos());
		}
		add(p0);
		pack();
		setVisible(true);
		this.setLocationRelativeTo(null);
	}
	
	private void createController() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// Joueurs
		for (final IPlayer pl : model.getPlayers()) {
			pl.addPropertyChangeListener(new PropertyChangeListener() {
				public void propertyChange(PropertyChangeEvent arg0) {
					balances.get(pl).setText(separate(pl.getBalance()) + " €");
				}
			});
		}
		// Dés
		rollMoveDice.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				model.rollMoveDice();
			}
		});
		rollBuildDice.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				model.rollBuildDice();
			}
		});
		// Fin de tour
		endTurn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try { model.endTurn(); }
				catch (WaitingOperationException e1) { e1.printStackTrace(); }
			}
		});
		// Cases
		for (final Entry<Entry<ICase, Integer>, JButton> e : sideButtons.entrySet()) {
			e.getValue().addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e1) {
					try {
						model.buildEntry(e.getKey().getKey(), e.getKey().getValue());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
		}
		// Propriétés
		for (final IProperty p : buy.keySet()) {
			buy.get(p).addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					try { model.buy(p); }
					catch (Exception e1) { e1.printStackTrace(); }
				}
			});
			addBuilding.get(p).addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					try { model.build(p); }
					catch (Exception e1) { e1.printStackTrace(); }
				}
			});
			addFunArea.get(p).addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					try { model.addFunArea(p); }
					catch (Exception e1) { e1.printStackTrace(); }
				}
			});
		}
		
		// Listeners sur modèle
		model.addPropertyChangeListener(new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent evt) {
				String e = evt.getPropertyName();
				/* ************************************************ */
				/* Dés                                              */
				/* ************************************************ */
				if (e.equals("beginMoveDice")) {
					rollMoveDice.setEnabled(false);
					moveDiceValue.setText("" + evt.getNewValue());
					model.walkPlayer();
				} else if (e.equals("moveDice")) {
					moveDiceValue.setText("" + evt.getNewValue());
				/* ************************************************ */
				/* Loyer                                            */
				/* ************************************************ */	
				} else if (e.equals("toPay")) {
					try {
						model.pay();
					} catch (Exception e1) { e1.printStackTrace(); }
				/* ************************************************ */
				/* Circuit                                          */
				/* ************************************************ */	
				} else if (e.equals("position")) {
					ICase oldCase = (ICase) evt.getOldValue();
					ICase newCase = (ICase) evt.getNewValue();
					if (oldCase != ICircuit.STARTING_CASE) {
						casesCenter.get(oldCase).setText(" ");
					}
					IPlayer current = model.getCurrentPlayer();
					casesCenter.get(newCase).setText("" + current.getName().charAt(0));
					casesCenter.get(newCase).setForeground(colors.get(current));
				/* ************************************************ */
				/* Joueurs                                         */
				/* ************************************************ */	
				} else if (e.equals("current")) {
					IPlayer oldP = (IPlayer) evt.getOldValue();
					IPlayer newP = (IPlayer) evt.getNewValue();
					names.get(oldP).setBorder(null);
					names.get(newP).setBorder(BorderFactory.createEtchedBorder());
				} else if (e.equals("beginTurn")) {
					if ((Boolean) evt.getNewValue()) {
						initButtons();
					} else {
						majButtons();
					}
				/* ************************************************ */
				/* Propriétés                                      */
				/* ************************************************ */	
				} else if (e.equals("buy")) {
					if (!(Boolean) evt.getNewValue()) {
						for (IProperty p : buy.keySet()) {
							buy.get(p).setEnabled(false);
						}
					} else {
						ICase c = model.getCircuit().getCase(model.getCurrentPlayer());
						for (IProperty p : buy.keySet()) {
							buy.get(p).setEnabled(model.getOwner(p) != model.getCurrentPlayer()
									&& model.getHotelsArea().getBuiltNb(p) == 0
									&& model.getCurrentPlayer().getBalance() >= p.getCost()
									&& (c.getProperty(ICase.RIGHT_SIDE) == p || c.getProperty(ICase.LEFT_SIDE) == p));
						}
					}
				} else if (e.equals("owner")) {
					JLabel lab = owners.get(evt.getOldValue());
					IPlayer p = (IPlayer) evt.getNewValue();
					lab.setForeground(colors.get(p));
					lab.setText(p.getName());
					colorize((IProperty) evt.getOldValue(), p);
				}
			}
		});

		names.get(model.getCurrentPlayer()).setBorder(BorderFactory.createEtchedBorder());
		initButtons();
	}
	
	// OUTILS
	
	private void colorize(IProperty prop, IPlayer p) {
		for (Entry<Entry<ICase, Integer>, JLabel> e : sideLabels.entrySet()) {
			if (e.getKey().getKey().getProperty(e.getKey().getValue()) == prop) {
				e.getValue().setBackground(colors.get(p));
			}
		}
	}
	
	private void majButtons() {
		endTurn.setEnabled(model.mustPay() == 0L);
		rollMoveDice.setEnabled(model.getCurrentRentable() != null);
	}
	
	private void initButtons() {
		endTurn.setEnabled(false);
		rollMoveDice.setEnabled(true);
		rollBuildDice.setEnabled(false);
		for (Entry<Entry<ICase, Integer>, JButton> e : sideButtons.entrySet()) {
			e.getValue().setEnabled(false);
		}
		for (IProperty p : buy.keySet()) {
			buy.get(p).setEnabled(false);
			addBuilding.get(p).setEnabled(false);
			addFunArea.get(p).setEnabled(false);
		}
	}
	
	private String separate(long number) {
		String res = "";
		long pas = 1000;
		long pre;
		String toAdd;
		do {
			pre = number % pas;
			number /= pas;
			toAdd = "" + pre;
			if (number > 0 && pre < 100) {
				toAdd = "0" + toAdd;
				if (pre < 10) {
					toAdd = "0" + toAdd;
				}
			}
			res = toAdd + " " + res;
			pas *= 1000;
		} while (number > 0);
		return res;
	}
	
	private String getBuiltFor(IProperty p) {
		String res = "";
		int i = 0;
		while (i < model.getHotelsArea().getBuiltNb(p)) {
			res += "█  ";
			++i;
		}
		while (i < p.getCapacity()) {
			res += "-  ";
			++i;
		}
		if (model.getHotelsArea().hasFunArea(p)) {
			res += "_+_+_";
		} else {
			res += "_____";
		}
		return res;
	}
	
	private JPanel getInfos() {
		JPanel res0 = new JPanel(null); {
			res0.setLayout(new BoxLayout(res0, BoxLayout.Y_AXIS));
			JPanel res = new JPanel(new GridLayout(0, 2)); {
				JPanel q;
				IProperty[] properties = Properties.values();
				for (IProperty p : properties) {
					q = new JPanel(null); {
						q.setLayout(new BoxLayout(q, BoxLayout.Y_AXIS));
						q.setBorder(BorderFactory.createEtchedBorder());
						q.add(new JLabel(p.getName()));
						JPanel r = new JPanel(new GridLayout(0, 2)); {
							r.add(new JLabel("Coût"));
							r.add(new JLabel(separate(p.getCost()) + " / " + separate(p.getReCost())));
							r.add(new JLabel("Entrée"));
							r.add(new JLabel(separate(p.getEntryCost())));
							for (int i = 0; i < p.getCapacity(); ++i) {
								r.add(new JLabel("Bâtiment " + (i + 1)));
								r.add(new JLabel(separate(p.getBuildingCost(i)) + " | " + separate(p.getRent(i)) + "/j"));
							}
							r.add(new JLabel("Fun Area"));
							r.add(new JLabel(separate(p.getFunAreaCost()) + " | " + separate(p.getRent(p.getCapacity(), true)) + "/j"));
						}
						q.add(r);
					}
					res.add(q);
				}
			}
			res0.add(res);
			res = new JPanel(); {
				JLabel lab;
				for (CaseStates s : CaseStates.values()) {
					lab = new JLabel(s.name());
					lab.setBackground(caseColors[s.ordinal()]);
					lab.setOpaque(true);
					res.add(lab);
				}
			}
			res0.add(res);
		}
		return res0;
	}

}
